/***************************************************************************
 *   Copyright (C) 2008 by Eduardo Gurgel Pinho                            *
 *   edgurgel@gmail.com                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
***************************************************************************/

#include "photon_map.h"

#include "photontracer.h"
#include "photon.h"
#include "kd_tree.h"
#include "object/object.h"
#include "common/color.h"
#include "math/vector.h"
#include "math/ray.h"

#include <algorithm>
#include <cmath>

namespace Sirat {

namespace Photon_Mapping {

Photon_Map::Photon_Map(void) : kd_tree(0) { 
	photontracer = 0;
}

Photon_Map::~Photon_Map(void) { 
	if(kd_tree) delete kd_tree;
}


void Photon_Map::insert(Photon* _photon) {
	if(_photon != 0) 
		photon_list.push_back(_photon);
}

void Photon_Map::scale(const double _n) {
	
	const double scale = 1.0/_n;
		
	std::vector<Photon*>::iterator i;
	
	for( i = photon_list.begin(); i != photon_list.end(); i++ ) {
		(*i)->set_color((*i)->get_color()*scale);
	}
}

int Photon_Map::get_size(void) {
	return photon_list.size();
}

void Photon_Map::clear(void) {
	photon_list.clear();
}

Kd_Tree* Photon_Map::get_kd_tree(void) const{
	return kd_tree;
}

void Photon_Map::destroy_kd_tree(void) {
	if(kd_tree)	delete kd_tree;
	kd_tree = 0;
}

Kd_Tree* Photon_Map::construct_kd_tree_rec(void) {
	
	kd_tree = new Kd_Tree();
	
	kd_tree->root = construct_kd_tree(photon_list,1);
	
	photon_list.clear();
	return kd_tree;
	
}

Kd_Node* Photon_Map::construct_kd_tree(std::vector<Photon*>& _list,unsigned int _order) {
		
	if(_list.size() != 0) {
		if(_order != 3) _order++;
		else _order = 1;
		Photon::set_order(_order);
		
		/* Getting a  pivot */
		std::vector<Photon*> left_list;
		std::vector<Photon*> right_list;
		std::vector<Photon*>::iterator pivot;
				
		if(_list.size() > 1) {
			pivot = choose_pivot(_list,left_list,right_list);
		} else pivot = _list.begin();
		
		Photon* photon = *pivot;
	
		/* Choosing cut value */
		Math::Vector value = (photon->get_position());
		
		/**
		 * Creating node 
		 */
		
		Kd_Node* node = new Kd_Node(value,_order);
		node->add_photon(photon);
				
		
		node->left = construct_kd_tree(left_list,_order);
		node->right = construct_kd_tree(right_list,_order);
		return node;
	}
	else { 
		return 0;
		
	}
}

Common::Color Photon_Map::irradiance_estimate(Object::Object* _object,const Math::Ray& _ray,const Math::Vector& _point,const unsigned int _sample_photons) const {
	if(kd_tree != 0) {
		std::vector<tuple<Photon*,double> > result;
		result = kd_tree->k_nearest_neighbour(_point,_sample_photons);
// 		if(result.size() < _sample_photons && photontracer != 0) {
			
// 			std::vector<tuple<Photon*,double> > new_result = photontracer->reverse_trace(_object,_point,1.0,_sample_photons - result.size(),30000);
// 			std::cout << "Gerados: " << new_result.size() << std::endl;
// 		}
					
		Common::Color scolor;
		unsigned int aux1 = result.size() - 1;
		double max_distance = result[aux1].get<1>();
		
		Math::Vector normal = _object->get_normal(_point);
		
		for(unsigned int i = 0; i < result.size(); i++ ) {
			Photon* photon = result[i].get<0>();
			double distance = result[i].get<1>();
			
			double wp = std::fabs((1.0 - (distance/1.5*max_distance)));
			//wp = 1.0;
			Math::Vector photon_direction = photon->get_direction();
			Math::Vector photon_position = photon->get_position();
			Common::Color photon_color = photon->get_color();
						
			double weight = std::fabs(std::min(1.0,photon_direction.dot_product(normal)));
			//double weight = 1.0;
			scolor = scolor + weight*photon_color*wp;
			
		}
		return 200.0*scolor*((1.0/(M_PI*max_distance*max_distance))*(1.0 - 2.0/(3.0*1.5)));
	}
	else return Common::Color();	
}

std::vector<Photon*>::iterator Photon_Map::choose_pivot(std::vector<Photon*>& _list,std::vector<Photon*>& _left_list,std::vector<Photon*>& _right_list) {
	/* Sort and return the median */
	stable_sort(_list.begin(),_list.end(),compare);
	if(_list.size() == 1) {
		return _list.begin();
	}
	else if(_list.size() == 2) {
		_right_list.push_back(*(_list.begin() + 1));
		return _list.begin();
	} else {
		unsigned int n1;
		n1 = (_list.size()/2) - ((_list.size()%2 == 1)?0:1);
		
		for(unsigned int i = 0; i < _list.size(); i++) {
			if(i < n1)
				_left_list.push_back(_list[i]);
			else if(i > n1)
				_right_list.push_back(_list[i]);
		}
		return _list.begin()+n1;
	}
}

}

}
